/**
 * 
 */
package idv.takeshi.software.productline.bookstore.domain.model.category;

import java.util.Set;

import org.apache.commons.lang.Validate;

import idv.takeshi.software.productline.bookstore.domain.model.book.Book;
import idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository;

/**
 * A model service for books paging related operations.
 * @author takeshi
 *
 */
public class BooksPagingService {
	
	/**
	 * A strategy for eliminate the duplicate codes.
	 * @author takeshi
	 *
	 */
	private static interface GetBooksStrategy {
		public Set<Book> getBooks(int startBookNo, int toBookNo);
	}
	
	/**
	 * A strategy for eliminate the duplicate codes.
	 * @author takeshi
	 *
	 */
	private static interface GetBooksSizeStrategry {
		public int getBooksSize();
	}
	
	/**
	 * Get best sellers by given pageNo and books amount contained per page.
	 * @param pageNo
	 * @param booksAmountPerPage
	 * @param category
	 * @return
	 */
	public Set<Book> getBestSellersByPageNoAndBookAmountPerPage(
			int pageNo, int booksAmountPerPage, final Category category){
		return getBooksByPageNoAndBookAmountPerPage(pageNo, booksAmountPerPage, new GetBooksStrategy(){
			
			public Set<Book> getBooks(int startBookNo, int toBookNo) {
				Validate.notNull(category, "category shall always not be null");
				return category.getAllBestSellers(startBookNo, toBookNo);
			}});
	}
	
	/**
	 * Get newest books by given pageNo and books amount contained per page.
	 * @param pageNo
	 * @param booksAmountPerPage
	 * @param category
	 * @return
	 */
	public Set<Book> getNewestBooksByPageNoAndBookAmountPerPage(
			int pageNo, int booksAmountPerPage, final Category category){
		return getBooksByPageNoAndBookAmountPerPage(pageNo, booksAmountPerPage, new GetBooksStrategy(){
			
			public Set<Book> getBooks(int startBookNo, int toBookNo) {
				Validate.notNull(category, "category shall always not be null");
				return category.getAllNewestBooks(startBookNo, toBookNo);
			}});
	}
	
	/**
	 * Get search books by given pageNo and books amount contained per page.
	 * @param pageNo
	 * @param booksAmountPerPage
	 * @param keyWord
	 * @param repository
	 * @return
	 */
	public Set<Book> getSearchBooksByPageNoAndBookAmountPerPage(
			int pageNo, int booksAmountPerPage, final String keyWord, final BookRepository repository){
		return getBooksByPageNoAndBookAmountPerPage(pageNo, booksAmountPerPage, new GetBooksStrategy(){
			
			public Set<Book> getBooks(int startBookNo, int toBookNo) {
				Validate.notNull(repository, "repository shall always not be null");
				return repository.findBooksByKeyWord(keyWord, startBookNo, toBookNo);
			}});
	}
	
	/**
	 * Calculate how many pages needed for books amount of the given category. 
	 * @param booksAmountPerPage
	 * @param category
	 * @return
	 */
	public int calculateMaxPageNo(int booksAmountPerPage, final Category category){
		Validate.notNull(category, "category shall always not be null");
		return getBooksSize(booksAmountPerPage, new GetBooksSizeStrategry(){
			public int getBooksSize() {
				return category.getAllBestSellers().size();
			}});
	}

	/**
	 * Calculate how many pages needed for books amount of the given book repository.
	 * @param booksAmountPerPage
	 * @param keyWord
	 * @param repository
	 * @return
	 */
	public int calculateMaxPageNo(int booksAmountPerPage, final String keyWord, 
			final BookRepository repository){
		Validate.notEmpty(keyWord, "keyWord shall always not be null/empty");
		Validate.notNull(repository, "repository shall always not be null");
		return getBooksSize(booksAmountPerPage, new GetBooksSizeStrategry(){
			public int getBooksSize() {
				return repository.findBooksAmountByKeyWord(keyWord);
			}});
	}
	
	private Set<Book> getBooksByPageNoAndBookAmountPerPage(int pageNo, int booksAmountPerPage,
			GetBooksStrategy strategy){
		Validate.notNull(strategy, "strategy shall always not be null");
		Validate.isTrue(pageNo > 0, "pageNo:" + pageNo + " shall always greater than 0");
		Validate.isTrue(booksAmountPerPage > 0, "booksAmountPerPage:" + booksAmountPerPage + 
				" shall always greater than 0");
		
		int pageStartIndex = pageNo - 1;
		int pageDestIndex = pageNo;
		int startBookNo = (pageStartIndex * booksAmountPerPage) + 1;
		int destBookNo = pageDestIndex * booksAmountPerPage;
		return strategy.getBooks(startBookNo, destBookNo);
	}

	/**
	 * @param booksAmountPerPage
	 * @param strategy
	 * @return
	 */
	private int getBooksSize(int booksAmountPerPage, GetBooksSizeStrategry strategy) {
		Validate.isTrue(booksAmountPerPage > 0, "booksAmountPerPage:" + booksAmountPerPage +
				" shall always greater than 0");
		Validate.notNull(strategy, "strategy shall always not be null");
		int maxPageNo = 0;
		int booksSize = strategy.getBooksSize();
		if(booksSize > booksAmountPerPage){
			maxPageNo = booksSize / booksAmountPerPage;
			maxPageNo = (booksSize % booksAmountPerPage) > 0? maxPageNo + 1: maxPageNo;
		} else{
			maxPageNo = 1;
		}
		return maxPageNo;
	}

}
